/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../smc_tbls.h"
#include "../smc_regs.h"
#include "../smc.h"

#include "ctx.h"
#include "private.h"
#include "ulv.h"
#include "smc_mc_reg_tbl.h"
#include "smc_cac_cfg_tbl.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_sw_regs_dump(struct pci_dev *dev)
{
	u32 tmp;

	L("SMC_SW_REGS START");

	tmp = smc_sw_r32(dev,SMC_SW_MCLK_CHG_TIMEOUT);
	L("SMC_SW_MCLK_CHG_TIMEOUT=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_DELAY_VREG);
	L("SMC_SW_DELAY_VREG=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_DELAY_EMERGENCY);
	L("SMC_SW_DELAY_EMERGENCY=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_SEQ_IDX);
	L("SMC_SW_SEQ_IDX=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_MVDD_CHG_TIME);
	L("SMC_SW_MVDD_CHG_TIME=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_MCLK_SWITCH_LIM);
	L("SMC_SW_MCLK_SWITCH_LIM=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_WATERMARK_THRESHOLD);
	L("SMC_SW_WATERMARK_THRESHOLD=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_PHASE_SHEDDING_DELAY);
	L("SMC_SW_PHASE_SHEDDING_DELAY=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_ULV_VOLT_CHANGE_DELAY);
	L("SMC_SW_ULV_VOLT_CHANGE_DELAY=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_MC_BLK_DELAY);
	L("SMC_SW_MC_BLK_DELAY=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_TICKS_PER_US);
	L("SMC_SW_TICKS_PER_US=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_CRTC_IDX);
	L("SMC_SW_CRTC_IDX=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_MCLK_CHANGE_BLK_CP_MIN);
	L("SMC_SW_MCLK_CHANGE_BLK_CP_MIN=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_MCLK_CHANGE_BLK_CP_MAX);
	L("SMC_SW_MCLK_CHANGE_BLK_CP_MAX=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_NON_ULV_PCIE_LINK_WIDTH);
	L("SMC_SW_NON_ULV_PCIE_LINK_WIDTH=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_TDR_IS_ABOUT_TO_HAPPEN);
	L("SMC_SW_TDR_IS_ABOUT_TO_HAPPEN=0x%08x",tmp);

	tmp = smc_sw_r32(dev,SMC_SW_VR_HOT_GPIO);
	L("SMC_SW_VR_HOT_GPIO=0x%08x",tmp);

	L("SMC_SW_REGS END");
}
#endif

#define VOLT_TIME_DEFAULT 1000
#define VBI_TIMEOUT 100000
void response_times_init(struct ctx *ctx)
{
	u32 volt_time;
	u32 delay;
	
	smc_sw_wr32(ctx->dev, 1, SMC_SW_MVDD_CHG_TIME);

	volt_time = (u32)ctx->atb_volt_time;
	if (volt_time == 0) /* 0 means gpu generation default */
		volt_time = VOLT_TIME_DEFAULT;

	delay = (volt_time * ctx->gpu_aux_clk) / 100;
	smc_sw_wr32(ctx->dev, delay, SMC_SW_DELAY_VREG);

	delay = (15000 * ctx->gpu_aux_clk) / 100;
	smc_sw_wr32(ctx->dev, delay, SMC_SW_DELAY_EMERGENCY);

	delay = (VBI_TIMEOUT * ctx->gpu_aux_clk) / 100;
	smc_sw_wr32(ctx->dev, delay, SMC_SW_MCLK_CHG_TIMEOUT);
	smc_sw_wr32(ctx->dev, 0xaa, SMC_SW_MC_BLK_DELAY);
}

#define BACK_BIAS_TIME_DEFAULT 1000
void smc_sw_regs_init(struct ctx *ctx)
{
	struct dev_drv_data *dd;

	LOG("smc software registers init");

	dd = pci_get_drvdata(ctx->dev);

	if (ctx->volt_caps & VOLT_CAPS_VDDC_PHASE_SHED_CTL_ENA)
		smc_sw_wr32(ctx->dev, ctx->atb_vddc_phase_shed_tbl.phase_delay,
						SMC_SW_PHASE_SHEDDING_DELAY);

	if (ctx->platform_caps & ATB_PP_PLATFORM_CAPS_VRHOT_GPIO_CONFIGURABLE) {
		u32 back_bias_time;

		back_bias_time = ctx->atb_back_bias_time;
		if (back_bias_time == 0) /* 0 means gpu generation default */
			back_bias_time = BACK_BIAS_TIME_DEFAULT;

		smc_sw_wr32(ctx->dev, back_bias_time, SMC_SW_VR_HOT_GPIO);
	}

	if (ctx->atb_ulv.lvls_n)
		smc_sw_regs_ulv_init(ctx);

	smc_mc_reg_tbl_sw_regs_init(ctx);
	smc_cac_cfg_tbl_sw_regs_init(ctx);
	response_times_init(ctx);
}
